1.7.2 社区版服务端

这里以社区版服务端源码(版本v1.10)为例,理解服务端SDK的设计。后端使用springboot+mybatis开发,依赖echatim-server-broker-community 中间件。

1.源码架构

`-- com
    |-- annotation
    |   |-- MethodFor.java # socket.io api注解Method
    |   |-- Permission.java
    |   `-- TopicFor.java # socket.io api注解Topic
    |-- commom
    |   |-- AppRespError.java # 社区版后端错误码定义
    |   |-- ConcurrentLimitedQueue.java
    |   |-- Config.java # springboot环境变量获取工具类
    |   |-- DBConst.java # 业务常量定义
    |   |-- DBEnum.java
    |   |-- LocalFileRespError.java # 社区版文件服务器错误码定义
    |   |-- LocalFileRoute.java
    |   |-- NumberGenerator.java
    |   |-- PermissionConst.java
    |   `-- Topic.java # 社区版Topic的定义, api规约定义
    |-- echatim
    |   |-- ApplicationWrapper.java
    |   |-- CommonMapper.java
    |   |-- broker
    |   |   |-- AppBrokerHook.java
    |   |   |-- BrokerConfig.java
    |   |   |-- SocketIOBroker.java # socket.io服务启动,Topic绑定,消息收发
    |   |   |-- localsvc
    |   |   |   |-- ITopicSender.java
    |   |   |   |-- TopicDispatcher.java # 处理socket.io接受到的消息,转发到Service
    |   |   |   `-- TopicSender.java # 把消息通过socket.io发送出去
    |   |   `-- storage
    |   |       |-- AppStorageEntry.java
    |   |       `-- event
    |   |           |-- ClientDisconnectMessage.java
    |   |           |-- ClientOnlineEvent.java
    |   |           `-- LoginEventMessage.java
    |   |-- config
    |   |   |-- EventBusConfig.java
    |   |   |-- FileDownloadConfig.java
    |   |   |-- MybatisPlusConfig.java
    |   |   |-- SocketIOConfig.java
    |   |   |-- Swagger2Config.java
    |   |   |-- TopicFilterConfig.java
    |   |   `-- ValidateConfig.java
    |   |-- controller # http api定义
    |   |   |-- ClientController.java
    |   |   |-- HistoryMessageController.java
    |   |   |-- LoginController.java
    |   |   |-- MessageController.java
    |   |   |-- RoomController.java
    |   |   |-- UserController.java
    |   |   |-- UserRelationController.java
    |   |   `-- fileserver # fileserver http api定义
    |   |       |-- FileApi.java
    |   |       |-- FileConfigDTO.java
    |   |       |-- FileServerController.java
    |   |       |-- FileSignatureCache.java
    |   |       |-- FileUploadCacheDTO.java
    |   |       |-- FileUploadCallbackForm.java
    |   |       |-- FileUploadRequestDTO.java
    |   |       `-- FileUploadRequestForm.java
    |   |-- dto
            ... 模型数据 ... 
    |   |-- entity
            ... 模型数据 ... 
    |   |-- filter
    |   |   |-- HttpTopicInterceptor.java
    |   |   |-- ProtocolHandlerInterceptor.java
    |   |   `-- TopicInterceptor.java
    |   |-- form
            ... 模型数据 ... 
    |   |-- helper
    |   |   `-- PageHelper.java
    |   |-- mapper
    |   |   |-- FileInfoMapper.java
    |   |   |-- MessageMapper.java
    |   |   |-- MessageStableOfflineMapper.java
    |   |   |-- RoomMapper.java
    |   |   |-- RoomUserMapper.java
    |   |   |-- SdkAppMapper.java
    |   |   |-- SdkUserMapper.java
    |   |   |-- UserMapper.java
    |   |   `-- UserRelationMapper.java
    |   `-- service # 业务逻辑实现
    |       `-- app
    |           |-- BaseService.java
    |           |-- HistoryMessageService.java
    |           |-- LoginService.java
    |           |-- MessageService.java
    |           |-- MessageStableOfflineService.java
    |           |-- RoomService.java
    |           |-- RoomUserService.java
    |           |-- SdkAppService.java
    |           |-- SdkUserService.java
    |           |-- UserRelationService.java
    |           |-- UserService.java
    |           `-- fileserver
    |               `-- FileInfoService.java
    |-- event
    |   |-- AppStartup.java
    |   `-- EventBus.java
    |-- exception # 一些全局异常, 若表单校验,业务异常等
    |   |-- FormValidationException.java
    |   |-- HttpGlobalExceptionAdvice.java
    |   |-- ServiceException.java
    |   `-- SocketGlobalExceptionAdvice.java
    `-- utils
        |-- Beans.java
        |-- Pair.java
        |-- ProtocolAnnotationUtils.java
        |-- Streams.java
        |-- Triple.java
        |-- UUIDUtils.java
        `-- mapper
            |-- IDMapper.java
            |-- KeyMapper.java
            `-- Mapper.java

2.echatim-server-broker-community 中间件的功能

  1. 转发中间件: 路由转发消息到socket.io用户所在的机器(单机, 机器两种情况的不同处理)
  2. 存储中间件: 抽象化sdk用到的存储插件(guava, redis等)
  3. 消息中间件: 抽象化sdk用到的消息插件(eventBus, kafka等)
  4. 一些常用的工具类

3.接收消息, 定义http api与socket.io api统一的规范约定

前端与后端使用http api, socket.io api 两种通讯,为统一两种api,定义了TOPIC, METHOD 两个概念来约定。

TOPIC: 话题, 可以理解成一个业务模块

METHOD: TOPIC下有多个METHOD, 一个METHOD对应后端的一个业务方法。

对于socket.io api: 后端是通过TOPIC, METHOD两个关键字来定位到是使用哪个业务方法来处理这个api的。如以下的登录认证socket.io api 定义。

@TopicFor(value = Topic.CONNECTION.name) // 定义该业务模块的TOPIC:CONNECTION
@Service
public class LoginService extends ServiceImpl<UserMapper, User> {
    ... 略 ...
    @MethodFor(value = Topic.CONNECTION.METHOD.AUTHORITY_REQUEST, consumer = UserLoginForm.class) // 绑定该业务方法处理对于的METHOD:AUTHORITY_REQUEST
    public Resp<String> authority(@Valid UserLoginForm userLoginForm){   
    }
    ... 略 ...
}

后端从socket.io接受到的客户端消息中,能获取到Topic, Method两个变量定义,然后通过 @TopicFor, @MethodFor 两个注解,找到对应的匹配的业务处理方法,这一过程是TopicDispatcher中处理的。

对于http api: 后端也是通过TOPIC, METHOD两个关键字来定位到是使用哪个Controller method来处理这个api的。

@RestController
@RequestMapping(Topic.CONNECTION.base_uri)
public class LoginController extends ApiController {
    @ApiOperation(value = "IM用户登录-登录")
    @PostMapping("/"+Topic.CONNECTION.METHOD.AUTHORITY_REQUEST)
    public Resp<String> sdkLogin(@RequestBody @Valid UserLoginForm userLoginForm) {
    }
}

后端通过拼接的Topic.CONNECTION.base_uri + "/" + AUTHORITY_REQUEST 能定位到对应的Controller method.

通过这样的约定与定义,可以最大化复用后端的Service 业务代码。

4.socket.io接受与发送

socket.io的接受与发送主要在SocketIOBroker, TopicDispatcher, TopicSender 三个类中处理。

SocketIOBroker: 1. 负责socket.io 协议服务在springboot的环境下启动,销毁; 2. 绑定socket.io 要监听的所有Topic定义; 3. 启动对应的中间件(存储, 事件, 转发模块等)

TopicDispatcher: 处理消息输入, 将输入的消息转发到对应Service中的method 中处理,并返回处理结果。

TopicSender: 给定socket.io 的clientId, 向该client发送消息。业务并不会直接调用TopicSender 发消息,而是交给转发中间件来处理。

5.发送即时消息的流程

发送一个即时消息的流程如下: 客户端调用发消息的API(sendMessage) -> 后端保存消息到数据库,并创建ClusterDispatcherEvent对象,发送ClusterDispatcherEvent对象到转发中间件-> 转发中间件查找socket.io client 所在的机器,往该机器发送消息队列消息 -> socket.io client 所在机器发消息给客户端

对于单台机器的情况,所有的socket.io 客户端都连接到同一台机器,因此流程可以简化,直接在当前机器上查找clientId与登录用户账号的映射关系,把消息转发到该用户即可。

results matching ""

    No results matching ""